﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tomahawk.Runtime.Resources;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using System.Collections;
using Microsoft.Xna.Framework.Content;

namespace Tomahawk.Runtime.Rendering.Materials
{
    #region SAS Effect Exceptions
    public class SASInvalidEffectException : Exception
    {
    }
    #endregion

    public sealed class SASEffectEnvironment
    {
        #region Transformations
        public Matrix world;
        public Matrix view;
        public Matrix projection;
        public float time = 0;
        public float elapsedTime = 0;
        #endregion

        #region Illumination
        public struct Light
        {
            public Vector4 position;
            public Vector4 direction;
            public Vector4 specular;
            public Vector4 ambient;
        }

        public Light light0;
        #endregion
    }

    public class SASMaterial
    {
        #region SASEffectParametersCollection
        public class SASEffectParametersCollection : IEnumerable
        {
            protected SASMaterial effect;

            public SASEffectParametersCollection(SASMaterial effect)
            {
                this.effect = effect;
            }
            public SASParameter this[string index]
            {
                get
                {
                    SASParameter parameter = null;
                    effect.parameterTable.TryGetValue(index.ToUpper(), out parameter);
                    return parameter;
                }
            }
            public T Get<T>(string index) where T : SASParameter
            {
                SASParameter parameter = this[index];
                return parameter != null ? (parameter as T) : null;
            }

            #region IEnumerable Members

            public IEnumerator GetEnumerator()
            {
                return effect.parameterTable.GetEnumerator();
            }

            #endregion
        }
        #endregion

        #region Render Mode
        public enum RenderModes
        {
            Color,
            Depth,
            RenderModeCount
        }
        public static RenderModes currentRenderMode = RenderModes.Color;
        #endregion

        #region Properties
        protected string effectFile = null;
        internal RenderTarget renderSource = null;
        internal RenderTarget renderTarget = null;

        protected Effect effect;
        public Effect Effect { get { return effect; } }

        protected SASTechnique[] preferredTechniques = new SASTechnique[(int) RenderModes.RenderModeCount];
        protected Dictionary<string, SASTechnique> techniqueTable = null;
        public Dictionary<string, SASTechnique> TechniqueTable { get { return techniqueTable; } }

        protected Dictionary<string, SASParameter> parameterTable = null;
        protected SASEffectParametersCollection parameters = null;
        public SASEffectParametersCollection Parameters { get { return parameters; } }
        #endregion

        public SASMaterial()
        { 
        }

        public SASMaterial(string effectFile, ContentManager content)
        {
            Initialize(effectFile, content);
        }

        public virtual bool Initialize(string effectFile, ContentManager content)
        {
            if (this.effectFile != null)
            {
                return false;
            }

            this.effectFile = effectFile;
            this.techniqueTable = new Dictionary<string, SASTechnique>();
            this.parameterTable = new Dictionary<string, SASParameter>();
            this.parameters = new SASEffectParametersCollection(this);

            Effect resource = content.Load<Effect>(effectFile);
            effect = resource.Clone(Engine.Instance.GraphicsDevice);

            foreach (EffectParameter parameter in effect.Parameters)
            {
                SASParameter sasParameter = SASParameterBuilder.Build(parameter);
                parameterTable.Add(sasParameter.Name.ToUpper(), sasParameter);
            }

            foreach (EffectTechnique technique in effect.Techniques)
            {
                SASTechnique sasTechnique = new SASTechnique();
                sasTechnique.Initialize(this, technique);
                techniqueTable.Add(sasTechnique.Name.ToUpper(), sasTechnique);
            }

            SASScriptParameter script = Parameters.Get<SASScriptParameter>("SCRIPT");
            if (script == null) throw new SASInvalidEffectException();

            bool result = script.Execute(this);

            if (preferredTechniques[(int)RenderModes.Color] == null)
            {
                techniqueTable.TryGetValue(effect.CurrentTechnique.Name.ToUpper(), out preferredTechniques[(int)RenderModes.Color]);
            }

            return result;
        }

        private SASTechnique GetFirstValidTechnique(string script)
        {
            string[] techniques = script.Split(new char[] { ':' });
            foreach (string technique in techniques)
            {
                SASTechnique sasTechnique = null;
                if (techniqueTable.TryGetValue(technique, out sasTechnique))
                {
                    if (sasTechnique.Valid)
                    {
                        return sasTechnique;
                    }
                }
            }

            return null;
        }

        public bool SelectTechniques(string script)
        {
            string[] tokens = script.Split(new char[] { '?' });

            string type = tokens[0].ToUpper();
            string value = tokens[1];

            switch (type)
            {
                case "TECHNIQUE":
                case "COLOR": preferredTechniques[(int) RenderModes.Color] = GetFirstValidTechnique(value); break;
                case "DEPTH": preferredTechniques[(int) RenderModes.Depth] = GetFirstValidTechnique(value); break;
            }

            effect.CurrentTechnique = preferredTechniques[(int) RenderModes.Color].Technique;

            return (effect.CurrentTechnique != null);
        }

        #region Update parameters
        public bool Update(SASEffectEnvironment environment)
        {
            foreach (KeyValuePair<string, SASParameter> pair in parameterTable)
            {
                if (pair.Value.AutoValue)
                {
                    pair.Value.Update(environment);
                }
            }
            return true;
        }
        #endregion

        #region Rendering
        public static void Begin(VertexBuffer vertices, IndexBuffer indices, int offset, VertexDeclaration declaration, GraphicsDevice device)
        {
            device.Indices = indices;
            device.VertexDeclaration = declaration;
            device.Vertices[0].SetSource(vertices, offset, declaration.GetVertexStrideSize(0));
        }

        public static void End()
        {
        }

        public virtual bool DrawQuad(GraphicsDevice gd)
        {
            return false;
        }

        public virtual bool DrawIndexedPrimitives(PrimitiveType primitiveType, int baseVertex, int minVertexIndex, int numVertices, int startIndex, int primitiveCount, GraphicsDevice device)
        {
            bool result = true;
            SASTechnique selectedTechnique = preferredTechniques[(int)currentRenderMode];

            if (selectedTechnique != null)
            {
                if (effect.CurrentTechnique != selectedTechnique.Technique)
                {
                    effect.CurrentTechnique = selectedTechnique.Technique;
                }

                effect.Begin();

                SASScript.Context context = new SASScript.Context(
                    Engine.Instance.EffectEnvironment,
                    this,
                    selectedTechnique,
                    null,
                    primitiveType,
                    baseVertex,
                    minVertexIndex,
                    numVertices,
                    startIndex,
                    primitiveCount,
                    device);

                result = selectedTechnique.DrawMesh(ref context);

                effect.End();
            }

            return result;
        }

        public virtual bool DrawMesh(ModelMesh mesh, ModelMeshPart part, GraphicsDevice device)
        {
            bool result = true;
            SASTechnique selectedTechnique = preferredTechniques[(int) currentRenderMode];

            if (selectedTechnique != null)
            {
                if (effect.CurrentTechnique != selectedTechnique.Technique)
                {
                    effect.CurrentTechnique = selectedTechnique.Technique;
                }

                effect.Begin();

                SASScript.Context context = new SASScript.Context(
                    Engine.Instance.EffectEnvironment,
                    this,
                    selectedTechnique,
                    null,
                    mesh,
                    part,
                    device);

                result = selectedTechnique.DrawMesh(ref context);

                effect.End();
            }

            return result;
        }

        public virtual bool DrawScene(GraphicsDevice device)
        {
            return false;
        }

        public virtual bool DrawExternal(string script, GraphicsDevice device)
        {
            return false;
        }

        #endregion
    }
}
